use anyhow::Result;
use async_std::sync::{RwLock, RwLockWriteGuard};
use diesel_adapter::{
    casbin::{CoreApi, DefaultModel, Enforcer},
    DieselAdapter,
};
use std::sync::Arc;

#[derive(Clone)]
pub struct RBAC {
    pub enforcer: Arc<RwLock<Enforcer>>,
}

impl std::fmt::Debug for RBAC {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RBAC").finish()
    }
}

const RBAC_MODEL_STRING: &str = r#"
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
"#;

async fn get_casbin(model_path: &str, rbac_database_url: &str) -> Result<Enforcer> {
    let m = DefaultModel::from_file(model_path)
        .await
        .unwrap_or(DefaultModel::from_str(RBAC_MODEL_STRING).await?);
    let a = DieselAdapter::new(rbac_database_url, 8)?;
    let mut e = Enforcer::new(m, a).await?;
    e.enable_auto_save(true);
    Ok(e)
}

impl RBAC {
    pub async fn get_rbac(model_path: &str, rbac_database_url: &str) -> RBAC {
        RBAC {
            enforcer: Arc::new(RwLock::new(
                get_casbin(model_path, rbac_database_url).await.unwrap(),
            )),
        }
    }
    pub async fn handler(&self) -> RwLockWriteGuard<'_, Enforcer> {
        self.enforcer.write().await
    }
}

#[cfg(test)]
mod tests {
    use diesel_adapter::casbin::CoreApi;

    use crate::rbac_core::get_casbin;

    #[test]
    fn it_works() {
        let rbac = tokio_test::block_on(get_casbin(
            "./src/rbac_model.conf".into(),
            "mysql://root:123456@127.0.0.1:3306/casbin".into(),
        ))
        .unwrap();

        println!(
            "{}",
            rbac.enforce(("bob", "/bob_data/resource2", "POST"))
                .unwrap()
        );
    }
}
